'''
作者: 魔帝叶竹轩
Python版本: 3.12.4
系统: Ubuntu 20.04.6 LTS x86_64
CPU: Intel i3-6100U (4) @ 2.300GHz
RAM: 256 GB
ROM: 4 GB
'''
#import matplotlib.pyplot as plt
import random
import itertools
import time
import math


MIN_x,MAX_x = 0,1000
MIN_y,MAX_y = 0,1000
num_of_cv = 2024  # 文明总数 num_of_cv < MAX_x * MAX_y
num_ranges = num_of_cv # 坐标范围
offset_distance = 10 
cosmic_energy = 1000000000000  # 宇宙可用能源 目前一个兆
# 生成随机坐标范围列表
def generate_random_coordinate_ranges():
    coordinate_ranges = []
    for _ in range(num_ranges):
        x_range = (random.uniform(MIN_x, MAX_x), random.uniform(MIN_x, MAX_x))
        y_range = (random.uniform(MIN_y, MAX_y), random.uniform(MIN_y, MAX_y))
        coordinate_ranges.append((x_range, y_range))
    return coordinate_ranges

# 对坐标范围进行随机偏移
def offset_coordinate_ranges(coordinate_ranges):
    offset_coordinate_ranges = []
    for x_range, y_range in coordinate_ranges:
        x_offset = random.uniform(-offset_distance, offset_distance)
        y_offset = random.uniform(-offset_distance, offset_distance)
        new_x_range = (int(x_range[0] + x_offset), int(x_range[1] + x_offset))
        new_y_range = (int(y_range[0] + y_offset), int(y_range[1] + y_offset))
        offset_coordinate_ranges.append((new_x_range, new_y_range))
    return offset_coordinate_ranges

# 生成坐标
def generate_coordinates(coordinate_ranges):
    coordinates = []
    for x_range, y_range in coordinate_ranges:
        for _ in range(10):   # 生成每个范围内的10个坐标
            x = int(random.uniform(x_range[0], x_range[1]))
            y = int(random.uniform(y_range[0], y_range[1]))
            coordinates.append((x, y))
    for i in coordinates:
        yield i

class Civilization:

    coordinate:tuple = (1,1)  # 平面坐标
    state:bool = True # False死亡 True存活
    attitude:int = 1 # 0友善 1中立 2好斗
    isPositive:bool = True # True积极探索 False消极保守
    id:str = 'aaaaaa' # a * 6 -> z * 6 最多有26**6个文明，文明到后面会遭受打击，但可能会有存活，重组文明，所以可以在原有id后加序号，如aaaaaa1代表这是aaaaaa文明的后裔
    foe:list = []  # 敌人
    pal:list = []  # 朋友
    discovered:list = []  # 发现过的文明
    father:object = []  # 子文明才有
    technological_level:int = 1  # 科技水平
    knowledge:int = 1  # 所拥有的知识
    population:int = 100  # 人口 单位 万
    economy:int = 10  # 经济
    battle_effectiveness:int = 50  # 战斗力
    time_of_existence:int = 1  # 存在的时间
    threshold:int = 1  # 技术爆炸的阈值
    scope:int = 1  # 探测范围
    score:int = 10000  # 评分，<= 0时，文明灭亡
    
    def __init__(self,
                 coordinate:tuple,  # 平面坐标
                 attitude:int,  # 态度
                 isPositive:bool,  # 是否积极
                 id:str,  # 文明id
                 technological_level:int,  # 科技水平
                 population:int, # 人口
                 knowledge:int,  # 所拥有的知识
                 economy:int,  # 经济
                 ) -> None:
        global cosmic_energy
        self.coordinate = coordinate
        self.attitude = attitude
        self.isPositive = isPositive
        self.id = id
        self.technological_level = technological_level
        self.population = population
        self.knowledge = knowledge
        self.economy = economy
        self.battle_effectiveness = (self.population + self.economy + self.knowledge ) * self.technological_level
        self.score = self.technological_level + self.knowledge + self.population + self.economy + self.battle_effectiveness
        cosmic_energy -= 1
        

def detection(CV:object,ALL_CV:list) -> list:
    '''探测周围文明'''
    found_civilizations = set()  # 使用集合自动去重，避免后续的list(set(...))

    x, y = CV.coordinate
    scope = CV.scope

    # 定义一个函数来检查坐标是否在范围内
    def in_range(nx, ny):
        return MIN_y <= nx < MAX_x and MIN_y <= ny < MAX_y

    # 扫描所有可能的邻近坐标
    for dx in range(-scope, scope + 1):
        for dy in range(-scope, scope + 1):
            if dx == 0 and dy == 0:  # 排除CV自身的位置
                continue
            nx, ny = x + dx, y + dy
            if in_range(nx, ny):
                neighbor = next((j for j in ALL_CV if j.coordinate == (nx, ny)), None)
                if neighbor:
                    found_civilizations.add(neighbor)

    # 更新已发现的文明列表，确保不重复添加
    CV.discovered.extend(found_civilizations - set(CV.discovered))  # 只添加新发现的

    return list(found_civilizations)  # 返回列表形式的结果


def move_towards_target(CV:object, target_pos:tuple, speed=1) -> tuple['步数','最后的速度']:
    current_pos = CV.coordinate
    #print(f'{current_pos} --> {target_pos}')
    steps = 0
    x_diff, y_diff = target_pos[0] - current_pos[0], target_pos[1] - current_pos[1]
    direction_changes = 0  # 记录方向改变次数
    
    while current_pos != target_pos:
        if CV.isPositive:
           detection(CV,civilization_list)
        # 移动逻辑
        if abs(x_diff) >= abs(y_diff):  # 主要根据x轴方向移动
            if x_diff > 0:
                current_pos = (current_pos[0] + min(speed, abs(x_diff)), current_pos[1])
            else:
                current_pos = (current_pos[0] - min(speed, abs(x_diff)), current_pos[1])
            x_diff -= min(speed, abs(x_diff)) * (1 if x_diff > 0 else -1)
        else:  # 主要根据y轴方向移动
            if y_diff > 0:
                current_pos = (current_pos[0], current_pos[1] + min(speed, abs(y_diff)))
            else:
                current_pos = (current_pos[0], current_pos[1] - min(speed, abs(y_diff)))
            y_diff -= min(speed, abs(y_diff)) * (1 if y_diff > 0 else -1)
        
        # 检查是否达到增速条件
        if abs(x_diff) == 0 or abs(y_diff) == 0:  # 同向移动了至少10格
            if direction_changes < 1:  # 未改变方向或首次改变后
                speed += 1 if speed < 10 else 0  # 速度上限为10
            direction_changes = 0
        else:
            direction_changes += 1
        
        # 如果改变方向，则速度减少
        if (x_diff * y_diff != 0 and x_diff != 0 and y_diff != 0) or (x_diff == 0 and y_diff == 0):
            speed = max(1, speed - 1)  # 速度至少为1
        
        steps += 1
        #time.sleep(0.01)
    
    return steps, speed


def find_closest_points(points, a):
    '''在一个坐标列表寻与a最短的坐标 返回坐标和距离'''
    min_distance = float('inf')
    closest_points = []  # 用于存储所有距离最短的点
    
    for point in points:
        distance = math.sqrt((point[0] - a[0])**2 + (point[1] - a[1])**2)
        if distance < min_distance:
            min_distance = distance
            closest_points = [point]  # 更新最短距离并清空列表，重新添加当前点
        elif distance == min_distance:
            closest_points.append(point)  # 若距离相等，添加到最短距离点的列表中
    
    return closest_points, min_distance

def is_dead(CV) -> bool:
    if CV.score <= 0:
        CV.state = False
        try:
            civilization_list.remove(CV)
        except:
            pass
        return True
    else:
        return False

'''
# 生成的坐标不均匀
def generate_coordinates():
    coordinates = []
    ismin = MAX_y if MAX_x > MAX_y else MAX_x
    block_size = random.randint(2,ismin)
    num_blocks_x = MAX_x // block_size
    num_blocks_y = MAX_y // block_size

    while len(coordinates) < num_of_cv:
        for i in range(num_blocks_x):
            for j in range(num_blocks_y):
                if len(coordinates) >= num_of_cv:
                    break
                x = random.randint(i * block_size, (i + 1) * block_size - 1)
                y = random.randint(j * block_size, (j + 1) * block_size - 1)
                coordinates.append((x, y))

    coordinate = coordinates[:num_of_cv]
    for i in coordinate:
        yield i
'''

def isSub_civilization(CV:object) -> bool | tuple[str]:
    '''判断是否为子文明'''
    count = 0
    for char in CV.id:
        if char.isdigit():
            count += 1
    if count > 0:
        return (CV.id[(len(CV.id)-count):(len(CV.id) + 1)],CV.id[0:len(CV.id)-1])
    else:
        return False


def separatist(CV:object,
               coordinate:tuple,  # 平面坐标
               attitude:int,  # 态度
               isPositive:bool,  # 是否积极
               population:int, # 人口
               knowledge:int,  # 所拥有的知识
               economy:int,  # 经济
               battle_effectiveness:int,  # 战斗力
               score:int,  # 评分
               ) -> object:
    '''分裂子文明，无法攻击父文明'''
    num_str = isSub_civilization(CV)
    if num_str:
        # print(num_str)
        id = num_str[1] + str(int(num_str[0]) + 1)
    else:
        id = CV.id + '1'
    technological_level = CV.technological_level
    CV2 = Civilization(coordinate=coordinate,
                       attitude=attitude,
                       isPositive=isPositive,
                       id=id,
                       technological_level=technological_level,
                       population=population,
                       knowledge=knowledge,
                       economy=economy)
    civilization_list.append(CV2)
    CV.population -= population
    CV.knowledge -= knowledge
    CV.economy -= economy
    CV2.battle_effectiveness += CV.battle_effectiveness
    CV2.score += CV.score
    CV2.pal = CV.pal
    CV2.father = CV.father
    CV2.father.append(CV)
    CV2.foe = CV.foe
    CV2.discovered = CV.discovered
    CV2.scope = CV.scope
    CV2.threshold = CV.threshold
    return CV2
    coordinate:tuple = (1,1)  # 平面坐标
    attitude:int = 1 # 0友善 1中立 2好斗
    isPositive:bool = True # True积极探索 False消极保守




def attack(CV:object,target:object) -> bool:
    if target in CV.father:
        return False
    if (CV.battle_effectiveness >= target.battle_effectiveness) or (CV in target.father):
        CV.knowledge += target.knowledge
        if CV.technological_level >= target.technological_level:
            CV.technological_level += 1
        else:
            CV.technological_level = target.technological_level
        CV.economy += target.economy
        CV.population += target.population
        CV.battle_effectiveness += target.battle_effectiveness
        target.score = 0
        target.state = False
        CV.foe.remove(target)
        civilization_list.remove(target)
        return True
    elif CV.battle_effectiveness < target.battle_effectiveness:
        target.knowledge += CV.knowledge
        if target.technological_level >= CV.technological_level:
            target.technological_level += 1
        else:
            target.technological_level = CV.technological_level
        target.economy += CV.economy
        target.population += CV.population
        target.battle_effectiveness += CV.battle_effectiveness
        target.score = 0
        CV.state = False
        if CV in target.foe:
            target.foe.remove(CV)
        if CV not in target.discovered:
            target.discovered.append(CV)
        civilization_list.remove(CV)
        return False


def comprehensive_increase(ALL_CV:list) -> None:
    '''每个时间单位，整体增加属性'''
    global cosmic_energy
    for CV in ALL_CV:
        if CV.state:
            CV.time_of_existence += 1
            CV.knowledge += CV.threshold
            CV.technological_level += 1
            CV.economy += CV.threshold
            CV.population = ( CV.population + CV.economy ) * CV.threshold
            CV.battle_effectiveness = (CV.population + CV.economy + CV.knowledge ) * CV.technological_level
            CV.score = CV.technological_level + CV.knowledge + CV.population + CV.economy + CV.battle_effectiveness
            if CV.battle_effectiveness >= 1000: CV.scope += 10
            if CV.time_of_existence >= 10 and CV.knowledge >= 100 and CV.population >= 1000 and CV.economy >= 100 and CV.battle_effectiveness >= 10000:
                if random.random() <= 0.25: CV.threshold += 2
        cosmic_energy -= 1


def sorting(obj:list) -> list:
    CV = 0  # 总数
    n1 = 0  # 友善
    n2 = 0  # 中立
    n3 = 0  # 好斗
    n4 = 0  # 积极
    n5 = 0  # 消极
    for cv in obj:
        CV += 1
        if cv.state:
            match cv.attitude:
                case 0: n1 += 1
                case 1: n2 += 1
                case 2: n3 += 1
            if cv.isPositive: n4 += 1
            if not cv.isPositive: n5 += 1
    print(f'剩余{CV},(友善{n1},中立{n2},好斗{n3}),(积极{n4},消极{n5})')

    
def activity(CV:object) -> None:
    #CV = Civilization()
    if CV.state:
        match CV.isPositive:
            case True:  # 积极
                pick = random.choice(['x','y'])
                x,y = CV.coordinate
                if pick == 'x':
                    if x + 1 < MAX_x: x += 1
                elif pick == 'y':
                    if y + 1 < MAX_y: y += 1
                attitude = CV.attitude if random.random() <= 0.15 else 1  # 态度
                isPositive = True if random.random() <= 0.1 else False   # 是否积极
                population = int(CV.population / 0.25)  # 人口
                knowledge = int(CV.knowledge / 0.3)  # 所拥有的知识
                economy = int(CV.economy / 0.3)  # 经济
                battle_effectiveness = int(CV.battle_effectiveness / 0.5)  # 战斗力
                score = int(CV.scope / 0.25) # 评分
                CV2 = separatist(CV=CV,
                           coordinate=(x,y),
                           attitude=attitude,
                           isPositive=isPositive,
                           population=population,
                           knowledge=knowledge,
                           economy=economy,
                           battle_effectiveness=battle_effectiveness,
                           score=score)
                coordinate_list = []
                for i in civilization_list:
                    coordinate_list.append(i.coordinate)
                coordinate,steps = find_closest_points(coordinate_list,CV2.coordinate)
                pick = random.choice(['x','y'])
               # print(coordinate)
                if pick == 'x':
                    if x + 1 < MAX_x: x += 1
                elif pick == 'y':
                    if y + 1 < MAX_y: y += 1
                move_towards_target(CV2,(x,y))
                match CV.attitude:
                    case 2:
                        civilizations = detection(CV2,civilization_list)
                        for i in civilizations:
                            CV.foe.append(i)
                            CV2.foe.append(i)
                            if not attack(CV=CV2,target=i): break
                            


def synchronous(CV:object) -> None:
    for i in civilization_list:
        isSub_civilization(i)



def init_universe() -> None:  # 初始化一个宇宙
    global civilization_list
    global cosmic_energy
    
    civilization_list = []
    # 生成随机坐标范围列表
    coordinate_ranges = generate_random_coordinate_ranges()
    # 对坐标范围进行偏移
    offset_coordinate_range = offset_coordinate_ranges(coordinate_ranges)
    coordinates = generate_coordinates(offset_coordinate_range)
    for i in range(0,num_of_cv):
        letters = [chr(97 + (i // (26**j)) % 26) for j in range(5, -1, -1)]
        id = ''.join(letters)
        isPositive = random.choice([True, False])
        attitude=random.randint(0,2)
        technological_level = random.randint(1,10)
        population = random.randint(100,1000)
        economy = random.randint(1,10)
        knowledge = random.randint(1,10)
        coordinate = next(coordinates)
        CVs = Civilization(coordinate=coordinate,
                           attitude=attitude,
                           isPositive=isPositive,
                           id=id,
                           technological_level=technological_level,
                           population=population,
                           economy=economy,
                           knowledge=knowledge)

        civilization_list.append(CVs)
    while True:
        if cosmic_energy <= 0:
            print('宇宙重启')
            break
        comprehensive_increase(civilization_list)
        sorting(civilization_list)
        for j in civilization_list:
            activity(j)
        time.sleep(0.06)
'''
    x_values = []
    y_values = []
    for i in civilization_list:
        print(f"文明{i.id}:\n\
            坐标{i.coordinate}\n\
            状态{i.state}\n\
            态度{i.attitude}\n\
            是否积极{i.isPositive}\n\
            敌人{i.foe}\n\
            朋友{i.pal}\n\
            科技水平{i.technological_level}\n\
            人口{i.population}\n\
            知识{i.knowledge}\n\
            经济{i.economy}\n\
            战斗力{i.battle_effectiveness}\n\
            存在时间{i.time_of_existence}\n\
            评分{i.score}\n\n")
        x,y = i.coordinate
        x_values.append(x)
        y_values.append(y)

    plt.scatter(x_values, y_values, color='blue')  # color参数可以改变点的颜色
    plt.title('test')
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.show()
'''
if __name__ == '__main__':
    T1 = time.time()
    init_universe()
    print(f'{time.time() - T1}s')
